<template>
    <div class="base-tree">
        <!-- 筛选功能 -->
        <div class="filter" v-if="filterShow">
            <el-input
                placeholder="请输入过滤内容"
                v-model="filterText"
                :size="size | setSize(size)"
            >
                <i slot="suffix" class="el-input__icon el-icon-search"></i>
            </el-input>
        </div>
        <!-- 操作功能 -->
        <div class="option" v-if="optionShow">
            <el-button
                :size="size | setSize(size)"
                :type="isExpandAll ? 'primary' : 'info'"
                icon="el-icon-sort"
                @click="handelExpandAll"
            >
                展开/折叠
            </el-button>
            <el-button
                :size="size | setSize(size)"
                :type="checkedAll ? 'primary' : 'info'"
                :icon="checkedAll ? 'el-icon-close' : 'el-icon-check'"
                @click="handelCheckAll"
            >
                {{ checkedAll ? '取消' : '选中' }}
            </el-button>
            <el-button
                :size="size | setSize(size)"
                :type="isCheckStrictly ? 'info' : 'primary'"
                icon="el-icon-paperclip"
                @click="isCheckStrictly = !isCheckStrictly"
            >
                父子联动
            </el-button>
        </div>
        <div class="tree">
            <el-tree
                v-on="$listeners"
                ref="tree"
                v-loading="loading"
                :style="{ width: width }"
                :size="size | setSize(size)"
                :data="data"
                :empty-text="emptyText"
                :node-key="nodeKey"
                :props="defaultProps"
                :render-after-expand="renderAfterEpand"
                :load="load"
                :render-content="renderContent || undefined"
                :highlight-current="highlightCurrent"
                :default-expand-all="defaultExpandAll"
                :expand-on-click-node="expandOnClickNode"
                :check-on-click-node="checkeOnClickNode"
                :auto-expand-parent="autoExpandParent"
                :default-expanded-keys="defaultExpandKeys"
                :show-checkbox="showCheckbox"
                :check-strictly="isCheckStrictly"
                :default-checked-keys="defaultCheckedKeys"
                :current-node-key="currentNodeKey"
                :filter-node-method="filterNode"
                :accordion="accordion"
                :indent="indent"
                :icon-class="iconClass"
                :lazy="lazy"
                :draggable="draggable"
                :allow-drag="allowDrag"
                :allow-drop="allowDrop"
                @node-click="nodeClick"
                @node-contextmenu="nodeContextmenu"
                @check-change="checkChange"
                @check="check"
                @current-change="currentChange"
                @node-expand="nodeExpand"
                @node-collapse="nodeCollapse"
                @node-drag-start="nodeDragStart"
                @node-drag-enter="nodeDragEnter"
                @node-drag-leave="nodeDragLeave"
                @node-drag-over="nodeDragOver"
            />
        </div>
    </div>
</template>

<script>
export default {
    name: 'BaseTree',
    props: {
        /**
         * 树加载效果
         */
        loading: {
            type: Boolean,
            default: false
        },

        /**
         * 树宽度
         */
        width: {
            type: String,
            default: '100%'
        },

        /**
         * 数据
         */
        data: {
            type: Array,
            default: () => []
        },

        /**
         * 内容为空的时候展示的文本
         */
        emptyText: {
            type: String,
            default: () => '暂无数据'
        },

        /**
         * 数据标识
         */
        nodeKey: {
            type: String,
            default: 'id'
        },

        /**
         * 数据键值对
         */
        defaultProps: {
            type: Object,
            default: () => ({
                children: 'children',
                label: 'label'
            })
        },

        /**
         * 是否在第一次展开某个树节点后才渲染其子节点
         */
        renderAfterEpand: {
            type: Boolean,
            default: () => true
        },

        /**
         * 加载子树数据的方法，仅当 lazy 属性为true 时生效
         * function(node, resolve)
         */
        load: {
            type: Function,
            default: () => undefined
        },

        /**
         * 树节点的内容区的渲染 Function
         * Function(h, { node, data, store }
         */
        renderContent: {
            type: Function,
            default: undefined
        },

        /**
         * 是否高亮当前选中节点，默认值是 true
         */
        highlightCurrent: {
            type: Boolean,
            default: () => true
        },

        /**
         * 是否默认展开所有节点
         */
        defaultExpandAll: {
            type: Boolean,
            default: false
        },

        /**
         * 是否在点击节点的时候展开或者收缩节点， 默认值为 true，如果为 false，则只有点箭头图标的时候才会展开或者收缩节点。
         */
        expandOnClickNode: {
            type: Boolean,
            default: false
        },

        /**
         * 是否在点击节点的时候选中节点，默认值为 false，即只有在点击复选框时才会选中节点。
         */
        checkeOnClickNode: {
            type: Boolean,
            default: false
        },

        /**
         * 默认选中的项（复选情况）
         */
        defaultExpandKeys: {
            type: Array,
            default: () => []
        },

        /**
         * 是否显示选择框
         */
        showCheckbox: {
            type: Boolean,
            default: false
        },

        /**
         * 在显示复选框的情况下，是否严格的遵循父子不互相关联的做法，默认为 false
         */
        checkStrictly: {
            type: Boolean,
            default: false
        },

        /**
         * 默认勾选的节点的 key 的数组
         */
        defaultCheckedKeys: {
            type: Array,
            default: () => []
        },

        /**
         * 当前选中的数据（点击选中，单选）
         */
        currentNodeKey: {
            type: [Number, String]
        },

        /**
         * 是否每次只打开一个同级树节点展开
         */
        accordion: {
            type: Boolean,
            default: () => false
        },

        /**
         * 相邻级节点间的水平缩进，单位为像素
         */
        indent: {
            type: Number,
            default: () => 20
        },

        /**
         * 自定义树节点的图标
         */
        iconClass: {
            type: String,
            default: () => ''
        },

        /**
         * 是否懒加载子节点，需与 load 方法结合使用
         */
        lazy: {
            type: Boolean,
            default: () => false
        },

        /**
         * 是否开启拖拽节点功能
         */
        draggable: {
            type: Boolean,
            default: () => false
        },

        /**
         * 判断节点能否被拖拽
         */
        allowDrag: {
            type: Function,
            default: () => undefined
        },

        /**
         * 拖拽时判定目标节点能否被放置。type 参数有三种情况：'prev'、'inner' 和 'next'，分别表示放置在目标节点前、插入至目标节点和放置在目标节点后
         */
        allowDrop: {
            type: Function,
            default: () => undefined
        },

        /**
         * 展开子节点的时候是否自动展开父节点
         */
        autoExpandParent: {
            type: Boolean,
            default: () => true
        },

        /**
         * 控件的尺寸
         */
        size: {
            type: String,
            default: () => ''
        },

        /**
         * 过滤控件是否显示
         */
        filterShow: {
            type: Boolean,
            default: () => false
        },

        /**
         * 操作控件是否显示
         */
        optionShow: {
            type: Boolean,
            default: () => false
        }
    },
    watch: {
        /**
         * 监听当前选中项的key
         * @param {String} newVal
         */
        currentNodeKey(newVal) {
            if (newVal) {
                this.$refs.tree.setCurrentKey(newVal)
            }
        },
        checkStrictly: {
            handler: function(newVal) {
                this.isCheckStrictly = newVal
            },
            immediate: true
        },
        /**
         * 监听过滤的字段
         */
        filterText: {
            handler: function(newVal) {
                this.$nextTick(() => {
                    this.$refs.tree.filter(newVal)
                })
            },
            immediate: true
        }
    },
    data() {
        return {
            treeData: [],
            filterText: '',
            isExpandAll: false, // 是否展开所有节点
            checkedAll: false, // 是否全选
            isCheckStrictly: false // 是否父子联动
        }
    },
    methods: {
        /**
         * 单项点击事件
         * 共三个参数，依次为：传递给 data 属性的数组中该节点所对应的对象、节点对应的 Node、节点组件本身。
         * @param {Object} item
         * @param {Node} node
         * @param {VueComponent} self
         */
        nodeClick(item, node, self) {
            this.$emit('NodeClick', {
                item,
                node,
                self
            })
        },
        /**
         * 当某一节点被鼠标右键点击时会触发该事件
         * 共四个参数，依次为：event、传递给 data 属性的数组中该节点所对应的对象、节点对应的 Node、节点组件本身。
         * @param {PointerEvent} event
         * @param {Object} item
         * @param {Node} node
         * @param {VueComponent} self
         */
        nodeContextmenu(event, item, node, self) {
            this.$emit('NodeContextmenu', {
                event,
                item,
                node,
                self
            })
        },
        /**
         * 节点选中状态发生变化时的回调
         * 共三个参数，依次为：传递给 data 属性的数组中该节点所对应的对象、节点本身是否被选中、节点的子树中是否有被选中的节点
         * @param {Object} item
         * @param {Boolean} selfChecked
         * @param {Boolean} childrenNodeChecked
         */
        checkChange(item, selfChecked, childrenNodeChecked) {
            this.$emit('CheckChange', {
                item,
                selfChecked,
                childrenNodeChecked
            })
        },
        /**
         * 当复选框被点击的时候触发
         * 共两个参数，依次为：传递给 data 属性的数组中该节点所对应的对象、树目前的选中状态对象，包含 checkedNodes、checkedKeys、halfCheckedNodes、halfCheckedKeys 四个属性
         * @param {Object} item
         * @param {Object} checkedItem
         */
        check(item, checkedItem) {
            this.$emit('Check', {
                item,
                checkedItem
            })
        },
        /**
         * 当前选中节点变化时触发的事件
         * 共两个参数，依次为：当前节点的数据，当前节点的 Node 对象
         * @param {Object} item
         * @param {Node} node
         */
        currentChange(item, node) {
            this.$emit('CurrentChange', {
                item,
                node
            })
        },

        /**
         * 节点被展开时触发的事件	共三个参数，依次为：
         * 传递给 data 属性的数组中该节点所对应的对象、
         * 节点对应的 Node、
         * 节点组件本身
         * @param {Object} item
         * @param {Node} node
         * @param {VueComponent} self
         */
        nodeExpand(item, node, self) {
            node.expanded = true
            this.$emit('NodeExpand', {
                item,
                node,
                self
            })
        },
        /**
         * 节点被关闭时触发的事件	共三个参数，依次为：
         * 传递给 data 属性的数组中该节点所对应的对象、
         * 节点对应的 Node、
         * 节点组件本身
         * @param {Object} item
         * @param {Node} node
         * @param {VueComponent} self
         */
        nodeCollapse(item, node, self) {
            node.expanded = false
            this.$emit('NodeCollapse', {
                item,
                node,
                self
            })
        },
        /**
         * 节点开始拖拽时触发的事件	共两个参数，依次为：
         * 被拖拽节点对应的 Node、event
         * @param {PointerEvent} event
         * @param {Node} node
         */
        nodeDragStart(node, event) {
            this.$emit('NodeDragStart', {
                node,
                event
            })
        },
        /**
         * 拖拽进入其他节点时触发的事件	共三个参数，依次为：
         * 被拖拽节点对应的 Node、所进入节点对应的 Node、event
         * @param {Node} draggingNode
         * @param {Node} dropNode
         * @param {PointerEvent} event
         */
        nodeDragEnter(draggingNode, dropNode, event) {
            this.$emit('NodeDragEnter', {
                draggingNode,
                dropNode,
                event
            })
        },
        /**
         * 拖拽离开某个节点时触发的事件	共三个参数，依次为：
         * 被拖拽节点对应的 Node、
         * 所离开节点对应的 Node、event
         * @param {Node} draggingNode
         * @param {Node} dropNode
         * @param {PointerEvent} event
         */
        nodeDragLeave(draggingNode, dropNode, event) {
            this.$emit('NodeDragLeave', {
                draggingNode,
                dropNode,
                event
            })
        },
        /**
         * 在拖拽节点时触发的事件（类似浏览器的 mouseover 事件）
         * 被拖拽节点对应的 Node、
         * 所离开节点对应的 Node、event
         * @param {Node} draggingNode
         * @param {Node} dropNode
         * @param {PointerEvent} event
         */
        nodeDragOver(draggingNode, dropNode, event) {
            this.$emit('NodeDragLeave', {
                draggingNode,
                dropNode,
                event
            })
        },
        /**
         * 拖拽结束时（可能未成功）触发的事件
         * 共四个参数，依次为：
         * 被拖拽节点对应的 Node、
         * 结束拖拽时最后进入的节点（可能为空）、
         * 被拖拽节点的放置位置（before、after、inner）、
         * event
         * @param {Node} draggingNode
         * @param {Node} dropNode
         * @param {String} dropType
         * @param {PointerEvent} event
         */
        nodeDragEnd(draggingNode, dropNode, dropType, event) {
            this.$emit('NodeDragEnd', {
                draggingNode,
                dropNode,
                dropType,
                event
            })
        },
        /**
         * 拖拽成功完成时触发的事件	共四个参数，依次为：
         * 被拖拽节点对应的 Node、
         * 结束拖拽时最后进入的节点、
         * 被拖拽节点的放置位置（before、after、inner）、
         * event
         * @param {Node} draggingNode
         * @param {Node} dropNode
         * @param {String} dropType
         * @param {PointerEvent} event
         */
        nodeDrop(draggingNode, dropNode, dropType, event) {
            this.$emit('nodeDrop', {
                draggingNode,
                dropNode,
                dropType,
                event
            })
        },
        /**
         * 过滤搜索
         * @param {*} value
         * @param {*} data
         */
        filterNode(value, data) {
            if (!value) {
                return true
            }
            return data.label.indexOf(value) !== -1
        },
        /**
         * events
         */

        /**
         * 若节点可被选择（即 show-checkbox 为 true），则返回目前被选中的节点所组成的数组
         * (leafOnly, includeHalfChecked) 接收两个 boolean 类型的参数，
         * 1. 是否只是叶子节点，默认值为 false
         * 2. 是否包含半选节点，默认值为 false
         */
        getCheckedNodes(leafOnly = false, includeHalfChecked = false) {
            return this.$refs.tree.getCheckedNodes(leafOnly, includeHalfChecked)
        },
        /**
         * 若节点可被选择（即 show-checkbox 为 true），则返回目前被选中的节点的 key 所组成的数组
         * (leafOnly) 接收一个 boolean 类型的参数，若为 true 则仅返回被选中的叶子节点的 keys，默认值为 false
         */
        getCheckedKeys(leafOnly = false) {
            return this.$refs.tree.getCheckedKeys(leafOnly)
        },
        /**
         * 设置目前勾选的节点，使用此方法必须设置 node-key 属性
         * (nodes) 接收勾选节点数据的数组
         * @param {Array} nodes
         */
        setCheckedNodes(nodes) {
            this.$refs.tree.setCheckedNodes(nodes)
        },
        /**
         * 设置选中的key
         * @param {Array} list
         */
        setCheckedKeys(list) {
            if (list instanceof Array) {
                this.$refs.tree.setCheckedKeys(list)
            }
        },
        /**
         * 清空选中的key
         */
        resetChecked() {
            this.$refs.tree.setCheckedKeys([])
        },
        /**
         * 若节点可被选择（即 show-checkbox 为 true），则返回目前半选中的节点所组成的数组
         */
        getHalfCheckedNodes() {
            return this.$refs.tree.getHalfCheckedNodes()
        },
        /**
         * 若节点可被选择（即 show-checkbox 为 true），则返回目前半选中的节点的 key 所组成的数组
         */
        getHalfCheckedKeys() {
            return this.$refs.tree.getHalfCheckedKeys()
        },
        /**
         * 获取当前被选中节点的 key，使用此方法必须设置 node-key 属性，若没有节点被选中则返回 null
         */
        getCurrentKey() {
            return this.$refs.tree.getCurrentKey()
        },
        /**
         * 获取当前被选中节点的 data，若没有节点被选中则返回 null
         */
        getCurrentNode() {
            return this.$refs.tree.getCurrentNode()
        },
        /**
         * 通过 key 设置某个节点的当前选中状态，使用此方法必须设置 node-key 属性
         * (key) 待被选节点的 key，若为 null 则取消当前高亮的节点
         * @param {String,Number} key
         */
        setCurrentKey(key) {
            this.$refs.tree.setCurrentKey(key)
        },
        /**
         * 通过 node 设置某个节点的当前选中状态，使用此方法必须设置 node-key 属性
         * (node) 待被选节点的 node
         * @param {*} node
         */
        setCurrentNode(node) {
            this.$refs.tree.setCurrentNode(node)
        },
        /**
         * 根据 data 或者 key 拿到 Tree 组件中的 node
         * (data) 要获得 node 的 key 或者 data
         * @param { String | Number | Array } data
         */
        getNode(data) {
            this.$refs.tree.getNode(data)
        },
        /**
         * 删除 Tree 中的一个节点，使用此方法必须设置 node-key 属性
         * (data) 要删除的节点的 data 或者 node
         * @param { Array | Node } data
         */
        remove(data) {
            this.$refs.tree.remove(data)
        },
        /**
         * 为 Tree 中的一个节点追加一个子节点
         * (data, parentNode) 接收两个参数，
         * 1. 要追加的子节点的 data
         * 2. 子节点的 parent 的 data、key 或者 node
         * @param { Array } data
         * @param { Number | String | Array | Node} parentNode
         */
        append(data, parentNode) {
            this.$refs.tree.append(data, parentNode)
        },
        /**
         * 为 Tree 的一个节点的前面增加一个节点
         * (data, refNode) 接收两个参数，
         * 1. 要增加的节点的 data
         * 2. 要增加的节点的后一个节点的 data、key 或者 node
         * @param { Array } data
         * @param { Number | String | Array | Node } refNode
         */
        insertBefore(data, refNode) {
            this.$refs.tree.insertBefore(data, refNode)
        },
        /**
         * 为 Tree 的一个节点的后面增加一个节点
         * (data, refNode) 接收两个参数，
         * 1. 要增加的节点的 data
         * 2. 要增加的节点的前一个节点的 data、key 或者 node
         * @param { Array } data
         * @param { Number | String | Array | Node } refNode
         */
        insertAfter(data, refNode) {
            this.$refs.tree.insertAfter(data, refNode)
        },
        /**
         * 展开/收起 所有节点
         */
        handelExpandAll() {
            this.isExpandAll = !this.isExpandAll
            const treeNodes = this.$refs.tree.store.nodesMap
            for (let key of Object.keys(treeNodes)) {
                treeNodes[key].expanded = !treeNodes[key].expanded
            }
        },
        /**
         * 取消/选择所有选项
         */
        handelCheckAll() {
            if (!this.checkedAll) {
                //全选
                this.$refs.tree.setCheckedNodes(this.data)
                this.checkedAll = true
            } else {
                //取消选中
                this.$refs.tree.setCheckedKeys([])
                this.checkedAll = false
            }
            this.$nextTick(() => {
                this.$emit('CheckedAll', {
                    keys: this.getCheckedKeys(),
                    nodes: this.getCheckedNodes()
                })
            })
        }
    }
}
</script>

<style lang="scss" scoped>
.base-tree {
    @include box_flex(
        $width: 100%,
        $height: 100%,
        $direction: column,
        $justify: flex-start,
        $align: flex-start
    );

    padding: $margin-md;
    .filter {
        @include box_flex(
            $width: 100%,
            $height: auto,
            $direction: row,
            $justify: flex-start,
            $align: center
        );
    }
    .option {
        @include box_flex(
            $width: 100%,
            $height: auto,
            $direction: row,
            $justify: flex-start,
            $align: center
        );
    }
    .tree {
        @include box_flex(
            $width: 100%,
            $height: auto,
            $direction: row,
            $justify: flex-start,
            $align: flex-start,
            $flex-1: 1
        );

        overflow-y: auto;
        margin-top: 10px;
    }
}
</style>
